package com.googlecode.continuity.plugin.svnbrowser.server.service;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.BasicAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import com.googlecode.continuity.plugin.svnbrowser.client.ReadSVNException;
import com.googlecode.continuity.plugin.svnbrowser.client.SVNRepositoryHandle;

/**
 * ReadSVNServiceSVNKit
 *
 * @author Peter Schmitt <aragos@aragos.de>
 */
public class ReadSVNServiceSVNKit implements ReadSVNService {
// ------------------------------ FIELDS ------------------------------

  protected final Log logger = LogFactory.getLog(getClass());
  private boolean libraryInitialized;
  private Map<String, SVNRepository> repositories;

// --------------------------- CONSTRUCTORS ---------------------------

  public ReadSVNServiceSVNKit() {
    repositories = new HashMap<String, SVNRepository>();
    libraryInitialized = false;
  }

// ------------------------ INTERFACE METHODS ------------------------

// --------------------- Interface ReadSVNService ---------------------

  public SVNRepositoryHandle connect(String url, String username, String password) throws ReadSVNException {
    if (!libraryInitialized) {
      logger.debug("Setting up SVN library...");
      setupLibrary();
      logger.debug("Library set up, proceeding to create repository connection.");
    }

    String id = username + ":" + password + "@" + url;
    if (repositories.containsKey(id)) {
      return new SVNRepositoryHandle(id); // already connected
    }
    // TODO: update time stamp in cache

    SVNRepository repository;

    try {
      repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url));
    } catch (SVNException e) {
      ReadSVNException svnException =
          new ReadSVNException("An error occured while accessing repository for url [" + url + "].", e);
      logger.error(svnException);
      throw svnException;
    }

    // authenticate if credentials are provided. If not, authentication will be anonymous
    if (username != null && password != null) {
      logger.debug("Authenticating with repository.");
      BasicAuthenticationManager authManager = new BasicAuthenticationManager(username, password);
      repository.setAuthenticationManager(authManager);
    }

    repositories.put(id, repository);
    return new SVNRepositoryHandle(id);
  }

  public List<SVNDirEntry> getDirectoryContents(SVNRepositoryHandle handle, String directoryPath)
      throws ReadSVNException {
    return getDirectoryContents(handle, directoryPath, -1);
  }

  public List<SVNDirEntry> getDirectoryContents(SVNRepositoryHandle handle, String directoryPath, long revision)
      throws ReadSVNException {
    SVNRepository repository = repositories.get(handle.getRepositoryLocation());
    if (repository == null) {
      ReadSVNException svnException = new ReadSVNException("Requested repository is not available, a call to connect" +
          " is required before reading a repository. ID of handle provided: [" + handle.getRepositoryLocation() + "].");
      logger.error(svnException);
      throw (svnException);
    }

    SVNNodeKind nodeKind;
    try {
      nodeKind = repository.checkPath(directoryPath, revision);
    } catch (SVNException e) {
      ReadSVNException svnException = new ReadSVNException(
          "An error occured while accessing a repository directory at [" + directoryPath + "," + revision + "].",
          e);
      logger.error(svnException);
      throw svnException;
    }

    if (nodeKind == SVNNodeKind.NONE) {
      ReadSVNException svnException = new ReadSVNException(
          "The requested repository directory doesn't exist at [" + directoryPath + "," + revision + "].");
      logger.error(svnException);
      throw svnException;
    } else if (nodeKind == SVNNodeKind.FILE) {
      ReadSVNException svnException = new ReadSVNException(
          "The requested directory entry is a file instead at [" + directoryPath + "," + revision + "].");
      logger.error(svnException);
      throw svnException;
    }

    logger.debug("Requested directory [" + directoryPath + "," + revision + "] exists, reading content...");

    List<SVNDirEntry> ret = new LinkedList<SVNDirEntry>();
    try {
      repository.getDir(directoryPath, revision, true, ret);
    } catch (SVNException e) {
      ReadSVNException svnException = new ReadSVNException(
          "An error occurred while reading directory contents at [" + directoryPath + "," + revision + "].", e);
      logger.error(svnException);
      throw svnException;
    }
    logger.debug("Directory content read, [" + ret.size() + "] entries found.");

    return ret;
  }

  public String getTextFileContents(SVNRepositoryHandle handle, String filePath) throws ReadSVNException {
    return getTextFileContents(handle, filePath, -1);
  }

  public String getTextFileContents(SVNRepositoryHandle handle, String filePath, long revision)
      throws ReadSVNException {
    SVNRepository repository = repositories.get(handle.getRepositoryLocation());
    if (repository == null) {
      ReadSVNException svnException = new ReadSVNException("Requested repository is not available, a call to connect" +
          " is required before reading a repository. ID of handle provided: [" + handle.getRepositoryLocation() + "].");
      logger.error(svnException);
      throw (svnException);
    }

    SVNNodeKind nodeKind;
    try {
      nodeKind = repository.checkPath(filePath, revision);
    } catch (SVNException e) {
      ReadSVNException svnException = new ReadSVNException(
          "An error occured while accessing a repository file at [" + filePath + "," + revision + "].", e);
      logger.error(svnException);
      throw svnException;
    }

    if (nodeKind == SVNNodeKind.NONE) {
      ReadSVNException svnException =
          new ReadSVNException("The requested repository file doesn't exist at [" + filePath + "," + revision + "].");
      logger.error(svnException);
      throw svnException;
    } else if (nodeKind == SVNNodeKind.DIR) {
      ReadSVNException svnException = new ReadSVNException(
          "The requested file entry is a directory instead at [" + filePath + "," + revision + "].");
      logger.error(svnException);
      throw svnException;
    }

    Map fileProperties = new HashMap();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    String ret = "";
    try {
      repository.getFile(filePath, revision, fileProperties, baos);
    } catch (SVNException e) {
      ReadSVNException svnException = new ReadSVNException(
          "An error occurred while reading file contents at [" + filePath + "," + revision + "].", e);
      logger.error(svnException);
      throw svnException;
    }

    String mimeType = (String) fileProperties.get(SVNProperty.MIME_TYPE);
    if (SVNProperty.isTextMimeType(mimeType)) {
      ret = baos.toString();
    } else {
      ReadSVNException svnException = new ReadSVNException(
          "The requested file's mime type is not a text mime type. File contents cannot be returned.");
      logger.error(svnException);
      throw svnException;
    }
    ret = ret.replaceAll("\\r\\n", "\n");
    return ret;
  }

// -------------------------- OTHER METHODS --------------------------

  /*
  * Initializes the library to work with a repository via
  * different protocols.
  */

  private void setupLibrary() {
    /*
    * For http:// and https://
    */
    DAVRepositoryFactory.setup();
    /*
    * For svn:// and svn+xxx://
    */
    SVNRepositoryFactoryImpl.setup();

    /*
    * For file:///
    */
    FSRepositoryFactory.setup();
  }
}
